#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

#include "protocol.hpp"

namespace Client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t &serverport)
            : _serverip(serverip), _serverport(serverport), _sockfd(-1), _quit(false)
        {
        }
        void initClient()
        {
            // 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(2);
            }
            cout << "socket success: "
                 << " : " << _sockfd << endl;

            // 2. client要不要bind[必须要的]，client要不要显示的bind，需不需程序员自己bind？不需要！！！
            // 写服务器的是一家公司，写client是无数家公司 -- 由OS自动形成端口进行bind！-- OS在什么时候，如何bind
        }

        static void *readMessage(void *args)
        {
            int sockfd = *(static_cast<int *>(args));
            pthread_detach(pthread_self());
            while (true)
            {
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);
                size_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
                if (n > 0)
                {
                    buffer[n] = 0;

                    std::string package, text;
                    package = buffer;
                    if (!deLength(package, &text))
                        continue;
                    // "exitcode result"
                    Response resp;
                    resp.deserialize(text);
                    std::cout << "exitCode: " << resp.exitcode << std::endl;
                    std::cout << "result: " << resp.result << std::endl;
                }
            }

            return nullptr;
        }

        void run()
        {
            pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd);

            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);

            std::string line;
            char cmdline[1024];
            while (!_quit)
            {
                // std::cout << "mycal>>> ";
                // std::getline(std::cin, line); // 1+1

                // 如果用上面的方法输入要计算的式子到 line 里面的话，那么 "mycal>>> " 也是会重定向到 cat < fifo 的窗口的。用下面的方式就不会了。
                fprintf(stderr, "mycal>>> ");
                fflush(stderr);
                // 把输入的命令行里面的内容sendto出去
                fgets(cmdline, sizeof(cmdline), stdin);
                cmdline[strlen(cmdline) - 1] = 0;
                line = cmdline;

                // 用户如果输入回车-->空字符串就直接给提示
                if (line.empty() == true)
                {
                    std::cout << "请重新输入" << std::endl;
                    continue;
                }
                // 我们内部对外面用户输入的 " 1 +   3  " ... 处理一下 --> 把空格全部给去掉先
                std::string temp = "";
                // for (const auto &c : line)
                for (int i = 0; i < line.size(); i++)
                    if (line[i] != ' ')
                        temp += line[i];
                line = temp;

                // 得有一个 Request
                // 序列化 + 加报头 --> 发送出去
                Request req = ParseLine(line); // "1+1"
                std::string content;
                req.serialize(&content);
                std::string send_string = enLength(content);

                sendto(_sockfd, send_string.c_str(), send_string.size(), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }

        // 用户输入的line变成Request
        Request ParseLine(const std::string &line)
        {
            // 建议版本的状态机！
            //"1+1" "123*456" "12/0"
            int status = 0; // 0:操作符之前，1:碰到了操作符 2:操作符之后
            int i = 0;
            int cnt = line.size();
            std::string left, right;
            char op;
            while (i < cnt)
            {
                switch (status)
                {
                case 0:
                {
                    if (!isdigit(line[i]))
                    {
                        op = line[i];
                        status = 1;
                    }
                    else
                        left.push_back(line[i++]);
                }
                break;
                case 1:
                    i++;
                    status = 2;
                    break;
                case 2:
                    right.push_back(line[i++]);
                    break;
                }
            }
            std::cout << std::stoi(left) << " " << std::stoi(right) << " " << op << std::endl;
            return Request(std::stoi(left), std::stoi(right), op);
        }

        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;

        pthread_t _reader;
    };
} // namespace Client
